/*
 * @Author: LVGRAPE
 * @LastEditors: LVGRAPE
 */
// #include "at32f413.h"
// #include "systick.h"
#include <stdbool.h>
#include <string.h>
#include "bootloader.h"
#include "crc_func_def.h"
#include "CRC32.h"

#define CRC32_INIT_VALUE 0xFFFFFFFF
#define CRC32_XOR_VALUE 0xFFFFFFFF
DEF_CRC_FUNC(crc32_cal, 32, crc32__table);

void bl_pack_clear(bl_info_t *bl)
{
    memset(bl->pack.buffer, 0, sizeof(bl->pack.buffer));
    bl->pack.buffer_size = 0;
}
void bl_pack_send(bl_info_t *bl, uint8_t cmd, uint32_t addr, uint8_t *data, uint16_t data_size)
{
    bl_pack_clear(bl);
    bl->pack.cmd1 = cmd;
    bl->pack.cmd2 = (uint8_t)~cmd;
    bl->pack.addr = addr;
    memcpy(bl->pack.data, data, data_size);
    bl->pack.data_size = data_size;
    bl->pack.crc = crc32_cal(bl->pack.buffer + 4, bl->pack.data_size + 8, CRC32_INIT_VALUE, CRC32_XOR_VALUE, 1, 1);
    bl->pack.buffer_size = 12 + bl->pack.data_size;
    bl->write(bl->pack.buffer, bl->pack.buffer_size);
}
uint8_t bl_pack_recv(bl_info_t *bl)
{
    bl_pack_clear(bl);
    bl->pack.buffer_size = bl->read(bl->pack.buffer);
    if (bl->pack.buffer_size > 0)
    {
        uint32_t crc = crc32_cal(bl->pack.buffer + 4, bl->pack.buffer_size - 4, CRC32_INIT_VALUE, CRC32_XOR_VALUE, 1, 1);
        if (crc == bl->pack.crc)
        {
            return 0;
        }
    }
    return -1;
}
void bl_write_handle(bl_info_t *bl)
{
    if (bl->pack.addr >= bl->appAddr && bl->pack.addr <= (bl->appAddr + bl->appSize))
    {
        bl->flashWrite(bl->pack.addr, bl->pack.data, bl->pack.data_size);
        bl_pack_send(bl, CMD_ACK, 0, NULL, 0);
    }
    else
    {
        bl_pack_send(bl, CMD_NACK, 0, NULL, 0);
    }
}
void bl_read_handle(bl_info_t *bl)
{

    if (bl->pack.addr >= bl->appAddr && bl->pack.addr < (bl->appAddr + bl->appSize))
    {
        uint32_t sizeRemain = bl->appAddr + bl->appSize - bl->pack.addr;
        uint32_t readSize = bl->pack.data_size > sizeRemain ? sizeRemain : bl->pack.data_size;

        bl->flashRead(bl->pack.addr, bl->pack.data, readSize);

        bl->pack.cmd1 = CMD_ACK;
        bl->pack.cmd2 = (uint8_t)~CMD_ACK;
        bl->pack.addr = bl->pack.addr;
        bl->pack.data_size = readSize;
        bl->pack.crc = crc32_cal(&bl->pack.buffer[4], bl->pack.data_size + 8, CRC32_INIT_VALUE, CRC32_XOR_VALUE, 1, 1);
        bl->pack.buffer_size = 12 + bl->pack.data_size;
        bl->write(bl->pack.buffer, bl->pack.buffer_size);
    }
    else
    {
        bl_pack_send(bl, CMD_NACK, 0, NULL, 0);
    }
}

void bl_erase_handle(bl_info_t *bl)
{
    if (bl->pack.addr >= bl->appAddr && bl->pack.addr < (bl->appAddr + bl->appSize))
    {
        // bl_pack_send(bl, CMD_ACK, 0, NULL, 0);
        uint32_t sizeRemain = bl->appAddr + bl->appSize - bl->pack.addr;
        uint32_t eraseSize = 0;
        for (uint8_t i = 0; i < bl->pack.data_size; i++)
        {
            eraseSize <<= 8;
            eraseSize |= bl->pack.data[i];
        }
        eraseSize = eraseSize > sizeRemain ? sizeRemain : eraseSize;
        uint32_t endAddr = bl->pack.addr + eraseSize;
        bl->flashUnlock();
        for (uint32_t eraseAddr = bl->pack.addr; eraseAddr < endAddr; eraseAddr += bl->sectorSize)
        {
            bl->flashSectorErase(eraseAddr);
            bl_pack_send(bl, CMD_ACK, eraseAddr, (uint8_t *)&endAddr, 4);
        }
        bl->flashLock();
    }
    else
    {
        bl_pack_send(bl, CMD_NACK, 0, NULL, 0);
    }
}

void bl_jump_handle(bl_info_t *bl)
{
    if (bl->pack.addr >= bl->appAddr && bl->pack.addr < (bl->appAddr + bl->appSize))
    {
        bl_pack_send(bl, CMD_ACK, bl->pack.addr, NULL, 0);
        if (bl->jumpToAddr)
            bl->jumpToAddr(bl->pack.addr);
    }
    else
    {
        bl_pack_send(bl, CMD_NACK, 0, NULL, 0);
    }
}
void bl_W_fwInfo_handle(bl_info_t *bl)
{
    memset(&bl->fw_info, 0, sizeof(firmware_info_t));
    memcpy(&bl->fw_info, bl->pack.data, bl->pack.data_size);
    uint32_t crc = crc32_cal((const uint8_t *)&bl->fw_info + 4, sizeof(firmware_info_t) - 4, CRC32_INIT_VALUE, CRC32_XOR_VALUE, 1, 1);
    if (crc != bl->fw_info.infoCrc)
    {
        goto NackExit;
    }
    if (bl->fw_info.magic != FLASH_FW_MAGIC)
    {
        goto NackExit;
    }
    if (bl->fw_info.size > bl->appSize || bl->fw_info.size == 0)
    {
        goto NackExit;
    }
    if (bl->fw_info.start_addr < bl->appAddr || bl->fw_info.start_addr >= (bl->appAddr + bl->appSize))
    {
        goto NackExit;
    }
    if (bl->fw_info.whoAmI.ID != bl->whoAmI.ID || bl->fw_info.whoAmI.Serial != bl->whoAmI.Serial)
    {
        goto NackExit;
    }

    bl->flashSectorErase(bl->fwInfoAddr);
    bl->flashWrite(bl->fwInfoAddr, (uint8_t *)&bl->fw_info, sizeof(firmware_info_t));
    bl_pack_send(bl, CMD_ACK, 0, NULL, 0);
    return;

NackExit:
    bl_pack_send(bl, CMD_NACK, 0, NULL, 0);
}
void bl_R_fwInfo_handle(bl_info_t *bl)
{
    bl->flashRead(bl->fwInfoAddr, (uint8_t *)&bl->fw_info, sizeof(firmware_info_t));
    bl_pack_send(bl, CMD_ACK, bl->fwInfoAddr, (uint8_t *)&bl->fw_info, sizeof(firmware_info_t));
}
uint32_t bl_check_fw(bl_info_t *bl)
{
    uint32_t crc = 0;
    bl->flashRead(bl->fwInfoAddr, (uint8_t *)&bl->fw_info, sizeof(firmware_info_t));
    crc = crc32_cal((uint8_t *)&bl->fw_info + 4, sizeof(firmware_info_t) - 4, CRC32_INIT_VALUE, CRC32_XOR_VALUE, 1, 1);
    if (bl->fw_info.magic == FLASH_FW_MAGIC && bl->fw_info.size < bl->appSize && crc == bl->fw_info.infoCrc)
    {
        const uint8_t *fwData = (const uint8_t *)bl->fw_info.start_addr;
        if (bl->fw_info.bootTimes == 0)
        {
            /**First time,  check crc*/
            crc = crc32_cal(fwData, bl->fw_info.size, CRC32_INIT_VALUE, CRC32_XOR_VALUE, 1, 1);
            if (crc == bl->fw_info.FWcrc)
            {
                bl->jumpToAddr(bl->appAddr);
                return 0;
            }
        }
        else if (bl->fw_info.bootTimes == 0xFFFFFFFF)
        {
            /**system failure*/
            return 0;
        }
        else
        {
            bl->jumpToAddr(bl->appAddr);
            return 0;
        }
    }
    // bl_pack_send(bl, CMD_LOG, bl->fwInfoAddr, (uint8_t *)"fw crc error\r\n", 15);
    return 1;
}
void bootloader_handle(bl_info_t *bl)
{
#if 1
    bl->pack.buffer_size = bl->read(bl->pack.buffer);
    if (bl->pack.buffer_size != 0)
    {
        uint32_t crc = crc32_cal(bl->pack.buffer + 4, bl->pack.buffer_size - 4, CRC32_INIT_VALUE, CRC32_XOR_VALUE, 1, 1);
        uint8_t cmd2 = ~bl->pack.cmd2;
        if (bl->pack.crc == crc)
        {
            if (bl->pack.cmd1 == cmd2)
            {
                switch (bl->pack.cmd1)
                {
                case CMD_SYNC:
                    bl->synecd = true;
                    bl_pack_send(bl, CMD_ACK, 0, NULL, 0);
                    break;
                case CMD_VERSION:
                    bl_pack_send(bl, CMD_ACK, 0, (uint8_t *)&bl->version, 4);
                    break;
                    break;
                case CMD_GETID:
                    bl_pack_send(bl, CMD_ACK, 0, (uint8_t *)&bl->whoAmI, sizeof(bl->whoAmI));
                    break;
                case CMD_WRITE:
                    bl_write_handle(bl);
                    break;
                case CMD_READ:
                    bl_read_handle(bl);
                    break;
                case CMD_RESET:
                    bl_pack_send(bl, CMD_ACK, 0, NULL, 0);
                    if (bl->reset)
                        bl->reset();
                    break;
                case CMD_JUMP:
                    bl_jump_handle(bl);
                    break;
                case CMD_ERASE:
                    bl_erase_handle(bl);
                    break;
                case CMD_W_FW_INFO:
                    bl_W_fwInfo_handle(bl);
                    break;
                case CMD_R_FW_INFO:
                    bl_R_fwInfo_handle(bl);
                    break;
                default:
                    bl_pack_send(bl, CMD_NACK, 0, NULL, 1);
                    break;
                }
            }
            else
            {
                bl_pack_send(bl, CMD_NACK, 0, NULL, 2);
            }
        }
        else
        {
            bl_pack_send(bl, CMD_NACK, 0, NULL, 3);
        }
        memset(bl->pack.buffer, 0, bl->pack.buffer_size);
        bl->pack.buffer_size = 0;
    }
#endif
    if (bl->synecd == false)
    {
        if (bl->millis() > (bl->dt + 100))
        {
            // NOTE IAP timeout. Exit.
            bl_check_fw(bl);
            bl->dt = bl->millis();
        }
    }
}
